﻿using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Navigation;
using System.Windows.Threading;
using IronCow;
using Milkify.Data;
using Milkify.Input;
using Milkify.Interfaces;

namespace Milkify.Controllers
{
    internal class MainWindowController : Controller, IMainWindowController
    {
        #region Private Members
        private IViewFactory mViewFactory;

        private bool mIsFirstLoad = true;
        private IMainWindowView mView;
        private HotkeyContainer mHotkeyContainer;

        bool mNotifyIconBalloonTipShown = false;
        System.Windows.Forms.NotifyIcon mNotifyIcon;

        private IPopupView mCurrentKeyboardShortcutPopup;
        #endregion

        #region Construction
        public MainWindowController(ControllerContext context, Rtm rtm, IViewContainer viewContainer, IViewFactory viewFactory, IMainWindowView view)
            : base(context, rtm, viewContainer)
        {
            Rtm.PreviewGotResponse += new EventHandler(Rtm_PreviewGotResponse);
            Rtm.GotResponse += new EventHandler(Rtm_GotResponse);

            mView = view;
            mViewFactory = viewFactory;
            mHotkeyContainer = new HotkeyContainer();

            Properties.Settings.Default.SettingsSaving += new System.Configuration.SettingsSavingEventHandler(Default_SettingsSaving);

            InitializeNotifyIcon();
        }

        private void InitializeNotifyIcon()
        {
            if (mNotifyIcon == null)
            {
                mNotifyIcon = new System.Windows.Forms.NotifyIcon();
                mNotifyIcon.BalloonTipTitle = "Milkify";
                mNotifyIcon.BalloonTipText = "This is Milkify. Click the icon to show the application.";
                mNotifyIcon.Text = "Milkify";
                mNotifyIcon.Icon = System.Drawing.Icon.FromHandle(Properties.Resources.MilkifyIcon.GetHicon());
                mNotifyIcon.Click += new EventHandler(NotifyIcon_Click);

                mView.StateChanged += new EventHandler(View_StateChanged);
                mView.IsVisibleChanged += new DependencyPropertyChangedEventHandler(View_IsVisibleChanged);
            }
        }
        #endregion

        #region IMainWindowController Members
        public Window MainWindow
        {
            get { return mView as Window; }
        }
        #endregion

        #region Controller Members
        public override void Execute()
        {
            // Register hotkeys.
            RegisterHotkeys();

            // Listen to the navigating event to handle view refresh.
            ViewContainer.Navigating += new NavigatingCancelEventHandler(ViewContainer_Navigating);

            // Register command bindings.
            CommandBinding commandBinding = new CommandBinding(NavigationCommands.BrowseHome, BrowseHome, CanBrowseHome);
            mView.CommandBindings.Add(commandBinding);

            // Register input bindings.
            RegisterInputBindings();

            // Create view data.
            ICommand navigateToAboutPageCommand = new DecoupledCommand(NavigateToAboutPage);
            ICommand navigateToAccountCommand = new DecoupledCommand(NavigateToAccount, CanNavigateToAccount);
            ICommand searchTaskCommand = new DecoupledCommand<string>(SearchTask);
            ICommand addTaskCommand = new DecoupledCommand(AddTask);
            ICommand applicationSettingsCommand = new DecoupledCommand(NavigateToApplicationSettings, CanNavigateToApplicationSettings);
            var viewData = new MainWindowViewData()
            {
                NavigateToAboutPageCommand = navigateToAboutPageCommand,
                NavigateToAccountCommand = navigateToAccountCommand,
                SearchTaskCommand = searchTaskCommand,
                AddTaskCommand = addTaskCommand,
                ApplicationSettingsCommand = applicationSettingsCommand
            };
            mView.ViewData = viewData;

            // Restore previous window location and size.
            mView.Location = Properties.Settings.Default.WindowLocation;
            mView.Size = Properties.Settings.Default.WindowSize;
            mView.Closed += new EventHandler(View_Closed);

            // Wait for the Load event before redirecting to the Home page
            // because at the moment, the application is not running yet (App.Run() not called).
            mView.Loaded += new RoutedEventHandler(View_Loaded);

            // Restore the "pin to desktop" setting.
            ViewContainer.IsPinnedToDesktop = Properties.Settings.Default.PinToDesktop;
        }

        private void RegisterHotkeys()
        {
            try
            {
                mHotkeyContainer.AddTask.HotkeyPressed += new EventHandler(AddTask_HotkeyPressed);
                mHotkeyContainer.SearchTask.HotkeyPressed += new EventHandler(SearchTask_HotkeyPressed);
            }
            catch (Exception e)
            {
                MessageBox.Show( 
                    "An error occured while registering Milkify's hotkeys. Those hotkeys will be disabled.\n" + e.Message,
                    "Hotkey registration error",
                    MessageBoxButton.OK,
                    MessageBoxImage.Error);
            }
        }

        private void RegisterInputBindings()
        {
            Milkify.Diagnostics.MilkifyTraceSources.UISource.TraceEvent(System.Diagnostics.TraceEventType.Start, 0, "Registering main window input bindings.");

            mView.InputBindings.Clear();

            // Navigate back.
            InputBinding inputBinding = new InputBinding(NavigationCommands.BrowseBack, new GenericKeyGesture(Key.Back));
            mView.InputBindings.Add(inputBinding);

            // Go home.
            inputBinding = new InputBinding(NavigationCommands.BrowseHome,
                KeyboardShortcutHelper.GetMultipleGesture(
                    MultipleInputGesture_GestureMatched,
                    KeyboardShortcutHelper.GetKeyGesture(Key.G, "Go"),
                    KeyboardShortcutHelper.GetKeyGesture("GoHomeShortcut", "Home")));
            mView.InputBindings.Add(inputBinding);

            // Go to tag.
            ICommand showTagPickerCommand = new DecoupledCommand(ShowTagPicker, CanShowTagPicker);
            inputBinding = new InputBinding(showTagPickerCommand,
                KeyboardShortcutHelper.GetMultipleGesture(
                    MultipleInputGesture_GestureMatched,
                    KeyboardShortcutHelper.GetKeyGesture(Key.G, "Go"),
                    KeyboardShortcutHelper.GetKeyGesture("GoToTagShortcut", "To Tag")));
            mView.InputBindings.Add(inputBinding);

            // Go to task list.
            ICommand showTaskListPickerCommand = new DecoupledCommand(ShowTaskListPicker, CanShowTagPicker);
            inputBinding = new InputBinding(showTaskListPickerCommand,
                KeyboardShortcutHelper.GetMultipleGesture(
                    MultipleInputGesture_GestureMatched,
                    KeyboardShortcutHelper.GetKeyGesture(Key.G, "Go"),
                    KeyboardShortcutHelper.GetKeyGesture("GoToTaskListShortcut", "To List")));
            mView.InputBindings.Add(inputBinding);

            Milkify.Diagnostics.MilkifyTraceSources.UISource.TraceEvent(System.Diagnostics.TraceEventType.Stop, 0);
        }

        private void ViewContainer_Navigating(object sender, NavigatingCancelEventArgs e)
        {
            if (e.NavigationMode == NavigationMode.Refresh)
            {
                // If we're using a cache for IronCow, clear it.
                if (Rtm.Client != null && Rtm.Client.Cache != null)
                {
                    Milkify.Diagnostics.MilkifyTraceSources.UISource.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "Clearing response cache.");
                    Rtm.Client.Cache.Clear();
                }

                // If the current view is refreshable, set its refresh flag.
                IRefreshableView view = ViewContainer.CurrentView as IRefreshableView;
                if (view != null)
                {
                    Milkify.Diagnostics.MilkifyTraceSources.UISource.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "Refreshing current view.");
                    view.NeedsRefresh = true;
                }
            }
        }

        private void View_Loaded(object sender, RoutedEventArgs e)
        {
            if (mIsFirstLoad)
            {
                // No need to do this if the main window is reloaded...
                // (e.g. when the skin is changed).
                mIsFirstLoad = false;
                Redirect("Loading");
            }
        }
        #endregion

        #region Private Methods
        #region Hotkeys
        private void AddTask_HotkeyPressed(object sender, EventArgs e)
        {
            mView.Show();
            AddTask();
        }

        private void SearchTask_HotkeyPressed(object sender, EventArgs e)
        {
            mView.Show();
            mView.FocusSearchBox();
        }
        #endregion

        #region Keyboard Shortcuts
        private void MultipleInputGesture_GestureMatched(object sender, MultipleInputGestureEventArgs e)
        {
            if (e.EventArgs.OriginalSource is TextBox)
            {
                // For those keyboard shortcuts that are just letters ("G, L" for instance), we don't want
                // to intercept legitimate user input for controls like text boxes.
                e.CancelMatch = true;
                return;
            }

            e.EventArgs.Handled = true;

            MultipleInputGesture multipleInputGesture = (MultipleInputGesture)sender;

            // Create and show the keyboard shortcut popup if a new keyboard shortcut is starting
            // to match, and has several keystrokes in it.
            // Also, because several multiple-keystrokes shortcuts can have the same first keystroke,
            // they can be several hits here that can create and show the popup. Only show the first one,
            // and don't do anything if the popup has already been created.
            if (e.MatchIndex == 0 &&
                e.MatchType != GestureMatchType.Failed &&
                multipleInputGesture.Gestures.Count > 1 &&
                mCurrentKeyboardShortcutPopup == null)
            {
                mCurrentKeyboardShortcutPopup = (IPopupView)mViewFactory.Create("KeyboardShortcutPopup");
                mCurrentKeyboardShortcutPopup.Owner = MainWindow;

                var viewData = new KeyboardShortcutViewData();
                mCurrentKeyboardShortcutPopup.ViewData = viewData;

                // The keyboard focus moves to the popup as soon as it's shown, but we want to
                // keep it where it was, so we set it back immediately.
                var element = Keyboard.FocusedElement;
                mCurrentKeyboardShortcutPopup.Show();
                Keyboard.Focus(element);
            }

            // Update the keyboard shortcut popup for new matching keystrokes (including the first one).
            if (e.MatchType != GestureMatchType.Failed &&
                mCurrentKeyboardShortcutPopup != null)
            {
                var viewData = (KeyboardShortcutViewData)mCurrentKeyboardShortcutPopup.ViewData;
                string displayString = GetInputGestureDisplayString(multipleInputGesture.Gestures[e.MatchIndex]);
                switch (e.MatchIndex)
                {
                    case 0:
                        viewData.Verb = displayString;
                        break;
                    case 1:
                        viewData.Noun = displayString;
                        break;
                    case 2:
                        viewData.Context = displayString;
                        break;
                    default:
                        break;
                }
            }

            // If the keyboard shortcut is finished (failed or completed), close the popup.
            if (e.MatchType == GestureMatchType.Failed || e.MatchType == GestureMatchType.Complete)
            {
                if (mCurrentKeyboardShortcutPopup != null)
                {
                    mCurrentKeyboardShortcutPopup.Close();
                    mCurrentKeyboardShortcutPopup = null;
                }
            }
        }

        private string GetInputGestureDisplayString(InputGesture gesture)
        {
            string displayString = string.Empty;
            if (gesture is GenericKeyGesture)
                displayString = ((GenericKeyGesture)gesture).DisplayString;
            else if (gesture is KeyGesture)
                displayString = ((KeyGesture)gesture).DisplayString;
            return displayString;
        }
        #endregion

        #region Command Handlers
        private void BrowseHome(object sender, ExecutedRoutedEventArgs e)
        {
            while (ViewContainer.CanNavigateBack)
                ViewContainer.NavigateBack();
            e.Handled = true;
        }

        private void CanBrowseHome(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = (ViewContainer.CurrentView.GetName() != "Home");
            e.Handled = true;
        }

        private void SearchTask(string query)
        {
            Redirect("TaskList", "SearchTask", query);
        }

        private void AddTask()
        {
            Redirect("TaskEdit", "AddTask");
        }

        private void NavigateToApplicationSettings()
        {
            // Return home before showing the settings, so
            // we don't have to update pending pages too.
            // (still better than forcing the user to restart the app!)
            while (ViewContainer.CanNavigateBack)
                ViewContainer.NavigateBack();
            Redirect("Settings");
        }

        private bool CanNavigateToApplicationSettings()
        {
            return (ViewContainer.CurrentView.GetName() != "Settings");
        }

        private void NavigateToAboutPage()
        {
            Redirect("About");
        }

        private void NavigateToAccount()
        {
            Redirect("Account");
        }

        private bool CanNavigateToAccount()
        {
            return (ViewContainer.CurrentView.GetName() != "Account");
        }

        private void ShowTagPicker()
        {
            IPickerView tagPicker = (IPickerView)mViewFactory.Create("PickerPopup");
            tagPicker.Owner = MainWindow;

            var viewData = new PickerViewData();
            viewData.Labels.AddRange(Rtm.GetTags());
            tagPicker.ViewData = viewData;

            if (tagPicker.ShowDialog().GetValueOrDefault())
            {
                string tag = tagPicker.SelectedLabel;
                Redirect("TaskList", "NavigateToTag", tag);
            }
        }

        private bool CanShowTagPicker()
        {
            return (!(Keyboard.FocusedElement is TextBox));
        }

        private void ShowTaskListPicker()
        {
            IPickerView listPicker = (IPickerView)mViewFactory.Create("PickerPopup");
            listPicker.Owner = MainWindow;

            var viewData = new PickerViewData();
            foreach (var taskList in Rtm.TaskLists)
            {
                viewData.Labels.Add(taskList.Name);
            }
            listPicker.ViewData = viewData;

            if (listPicker.ShowDialog().GetValueOrDefault())
            {
                string taskListName = listPicker.SelectedLabel;
                TaskListViewData taskListViewData = new TaskListViewData(Rtm.TaskLists[taskListName]);
                Redirect("TaskList", "NavigateToTaskList", taskListViewData);
            }
        }

        private bool CanShowTaskListPicker()
        {
            return (!(Keyboard.FocusedElement is TextBox));
        }
        #endregion

        #region View Events
        private void View_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
        }

        private void View_Closed(object sender, EventArgs e)
        {
            if (mNotifyIcon != null)
            {
                mNotifyIcon.Dispose();
                mNotifyIcon = null;
            }

            Properties.Settings.Default.WindowLocation = mView.Location;
            Properties.Settings.Default.WindowSize = mView.Size;
            Properties.Settings.Default.Save();
        }

        private void View_StateChanged(object sender, EventArgs e)
        {
            if (mView.WindowState == WindowState.Minimized)
            {
                if (mNotifyIcon != null && Properties.Settings.Default.ShowInTaskbar)
                {
                    mView.Hide();
                    if (!mNotifyIconBalloonTipShown)
                    {
                        mNotifyIcon.ShowBalloonTip(2000);
                        mNotifyIconBalloonTipShown = true;
                    }
                }
            }
        }

        private void View_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            if (mNotifyIcon != null && Properties.Settings.Default.ShowInTaskbar)
            {
                mNotifyIcon.Visible = !mView.IsVisible;
                mView.ShowInTaskbar = mView.IsVisible && !ViewContainer.IsPinnedToDesktop;
            }
        }
        #endregion

        #region Tray Events
        private void NotifyIcon_Click(object sender, EventArgs e)
        {
            mView.Show();
            mView.WindowState = WindowState.Normal;
        }
        #endregion

        #region Rtm Events
        private void Rtm_PreviewGotResponse(object sender, EventArgs e)
        {
            if (mView.Dispatcher.CheckAccess())
            {
                ViewContainer.RequestProgressMaximum++;
            }
            else
            {
                mView.Dispatcher.BeginInvoke(DispatcherPriority.Render, (Action)(() => ViewContainer.RequestProgressMaximum++));
            }
        }

        private void Rtm_GotResponse(object sender, EventArgs e)
        {
            if (mView.Dispatcher.CheckAccess())
            {
                ViewContainer.RequestProgressCurrent++;
            }
            else
            {
                mView.Dispatcher.BeginInvoke(DispatcherPriority.Render, (Action)(() => ViewContainer.RequestProgressCurrent++));
            }
        }
        #endregion

        #region Settings Events
        private void Default_SettingsSaving(object sender, System.ComponentModel.CancelEventArgs e)
        {
            // Re-register the bindings.
            RegisterInputBindings();

            // Show/hide the tray icon if ShowInTaskbar changed.
            RefreshNotifyIcon();
        }

        private void RefreshNotifyIcon()
        {
            if (mNotifyIcon != null)
            {
                if (Properties.Settings.Default.ShowInTaskbar)
                {
                    mNotifyIcon.Visible = !mView.IsVisible;
                    mView.ShowInTaskbar = mView.IsVisible;
                }
                else
                {
                    mNotifyIcon.Visible = true;
                    mView.ShowInTaskbar = false;
                }
            }
        }
        #endregion
        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            mHotkeyContainer.Dispose();
        }

        #endregion
    }
}
